package org.fing.edu.uy.esbadp.motor.thread;

import static org.fing.edu.uy.esbadp.motor.constants.ConstantEvents.*;

import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import java.util.logging.Level;

import org.fing.edu.uy.esbadp.core.AdpFlowInterface;
import org.fing.edu.uy.esbadp.core.AdpFlowTree;
import org.fing.edu.uy.esbadp.core.entity.DMAdpAdaptationRequirement;
import org.fing.edu.uy.esbadp.core.entity.DMAdpEvent;
import org.fing.edu.uy.esbadp.core.entity.DMAdpEvents;
import org.fing.edu.uy.esbadp.core.entity.DMAdpStrategy;
import org.fing.edu.uy.esbadp.core.entity.DMRegisteredService;
import org.fing.edu.uy.esbadp.core.entity.GenericTreeNode;
import org.fing.edu.uy.esbadp.core.entity.KeyValuePair;
import org.fing.edu.uy.esbadp.core.entity.PropertiesMonitoring;
import org.fing.edu.uy.esbadp.core.interfaces.IAdpStrategy;
import org.fing.edu.uy.esbadp.logger.LoggerADP;
import org.fing.edu.uy.esbadp.motor.comunication.AdmAdaptationComunication;
import org.fing.edu.uy.esbadp.motor.comunication.AdmRequirementsComunication;
import org.fing.edu.uy.esbadp.motor.comunication.AdmServiceComunication;
import org.fing.edu.uy.esbadp.motor.register.RegistryChangeContract;
import org.fing.edu.uy.esbadp.motor.register.RegistryManager;
import org.fing.edu.uy.esbadp.motor.utils.MergeTreeUtils;
import org.fing.edu.uy.esbadp.motor.utils.ProcessTreeUtils;
import org.fing.edu.uy.esbadp.validation.ValidateArgument;

public class ThreadProcessNotification extends Thread {
	
	private static java.util.logging.Logger logger = LoggerADP.getLogger(ThreadProcessNotification.class.getSimpleName());

	private long serviceId;
	private String operName;
	private PropertiesMonitoring properties;
	
	
	public ThreadProcessNotification(long serviceId, PropertiesMonitoring properties) {
		this.serviceId = serviceId;
		this.properties = properties;
		this.operName = null;
	}
	
	@Override
	public void run() {

		if(logger.isLoggable(LoggerADP.DEBUG_LEVEL)){
			logger.log(LoggerADP.DEBUG_LEVEL, "Service Id : " + this.serviceId);
			for(KeyValuePair property : this.properties.propertiesAsList()){
				logger.log(LoggerADP.DEBUG_LEVEL, " --> New Property -> Name: {0}, Value: {1}.", new Object[]{property.getKey(), property.getValue()});
			}
		}

		try{
			RegistryChangeContract registry = RegistryChangeContract.getInstance();
			DMRegisteredService service = AdmServiceComunication.getVirtualService(this.serviceId);
			this.operName = "getVirtualService";
			
			this.operName = "getAdpFlowForService";
			AdpFlowTree actualFlowTree = AdmAdaptationComunication.getActualAdpFlow(this.serviceId);
			if(logger.isLoggable(LoggerADP.DEBUG_LEVEL)){
				if(actualFlowTree != null){
					logger.log(LoggerADP.DEBUG_LEVEL, actualFlowTree.toString());
				}
			}
			
			this.operName = "getEvaluationServiceRequeriments"; 
			List<String> serviceRequerimentsEvaluated = AdmRequirementsComunication.getEvaluationServiceRequeriments(this.serviceId, this.properties);
			if(ValidateArgument.isNotNullAndNotEmpty(serviceRequerimentsEvaluated)){
				
				this.operName = "getMonitoredEvents";
				List<String> monitoredEvents = getMonitoredEvents(serviceRequerimentsEvaluated);
				
				if (ValidateArgument.isNotNullAndNotEmpty(monitoredEvents)){
					
					//arboles de las estrategias seleccionadas
					GenericTreeNode<AdpFlowInterface> treeChangeContract = null;
					GenericTreeNode<AdpFlowInterface> treeNodeGEN = null;
					
					//si hay evento de cambio de wsdl se genera la estrategia con la transformacion 
					if(monitoredEvents.contains(EVENT_CONTRACT_CHANGE)){
						monitoredEvents.remove(EVENT_CONTRACT_CHANGE);
						
						this.operName = "getRequirementsForEvent";
						String contractChangeRequirement = getRequirementForEvent(EVENT_CONTRACT_CHANGE);
						
						this.operName = "getStrategiesSupported";
						String contractChangeStrategy = getStrategiesSupported(contractChangeRequirement).get(0);
						
						this.operName = "getAdpFlowTreeForStrategy";
						registry.addChange(this.serviceId, getAdpFlowTreeForStrategy(service, contractChangeStrategy, null, properties));
						treeChangeContract = registry.getChangeCopy(this.serviceId);
					}
					
					//se analiza otro evento al azar
					if(monitoredEvents.size() > 0){
						Random generator = new Random();
						
						// se obtiene aleatoriamente uno de los eventos que quedan por tratar
						int intEventRand = generator.nextInt(monitoredEvents.size());
						String eventName = monitoredEvents.get(intEventRand);
						
						//se obtienen todos requerimiento de adaptacion para el evento monitoreado
						this.operName = "getRequirementsForEvent";
						String requirementName = getRequirementForEvent(eventName);
						
						if (ValidateArgument.isNotNullAndNotEmpty(requirementName)){
							//se obtienen todas las estrategias que implementen el requerimeinto de adaptacion 
							this.operName = "getStrategiesSupported";
							List<String> strategyNames = getStrategiesSupported(requirementName);
							
							//Valido que existan estrategias que tratan el requerimeinto seleccionado
							if(ValidateArgument.isNotNullAndNotEmpty(strategyNames)){
								this.operName = "getEquivalentsServices";
								List<DMRegisteredService> equivalentsServices = AdmServiceComunication.getEquivalentsServices(this.serviceId);
								
								int strategyCant = strategyNames.size();
								for(int i = 0; i < strategyCant ; i++){
									//obtenemos una estrategia randomicamente
									int intStrategyRand = generator.nextInt(strategyNames.size());
									String strategyName = strategyNames.remove(intStrategyRand);
								
									//se obtienen todo los nodos para la estrategia seleccionada
									this.operName = "getAdpFlowTreeForStrategy";
									treeNodeGEN = getAdpFlowTreeForStrategy(service, strategyName, equivalentsServices, properties);
									
									//Si existe un arbol para la estrategia seleccionada corto la iteracion
									if(ValidateArgument.isNotNullAndNotEmpty(treeNodeGEN)){										
										break;
									}
								}
							}
						}
					}
					
					//si no habia cambio de contrato y existe un registro de cambio, envio el cambio de contrato registrado anteriromente
					if(!ValidateArgument.isNotNull(treeChangeContract) && registry.existChange(this.serviceId)){
						treeChangeContract = registry.getChangeCopy(this.serviceId);
					}
					GenericTreeNode<AdpFlowInterface> treeFinal = MergeTreeUtils.mergeTree(treeNodeGEN, treeChangeContract);
					
					//si se genero una nueva estrategia voy a notificar al ESB-Adaptativo
					if(ValidateArgument.isNotNull(treeFinal)){
						//proceso el arbol en caso que se use srv equivalente se obtiene las estraegias actuales de cada servicio
						treeFinal = ProcessTreeUtils.processTree(treeFinal, this.serviceId);
						//invoco al Administrador de Adaptacion para agregar una nueva adaptacion
						this.operName = "setAdpFlowForService";
						AdmAdaptationComunication.setAdpFlowForService(this.serviceId, treeFinal);
						//Print New Strategy in Log
						logger.info("New strategy for serviceId: " + this.serviceId + "\n" + treeFinal.toString());
					}	
				}
			}
		}catch (Exception ex) {
			logger.log(Level.SEVERE, "Exception when callOperation " + this.operName + ".", ex);
		}
	}
	
	
	/**
	 * Funcion encargarda de obtener la lista de Eventos disparados a partir de los requerimientos de servicio que no se cumplen.
	 * @param serviceRequerimentsEvaluated- Lista que contiene cada uno de los requerimientos de servicio que no se cumplen.
	 * @return List- retorna la lista de los eventos que se disparan a partir de los requerimientos de servicio que no se cumplen
	 */
	private List<String> getMonitoredEvents(List<String> serviceRequerimentsEvaluated) {
		List<String> result = new LinkedList<String>();
		if(ValidateArgument.isNotNull(serviceRequerimentsEvaluated)){
			DMAdpEvents events = RegistryManager.getInstance().getAdpEvents();
			for(DMAdpEvent event : events.getEvents()){
				List<String> unmetServiceReq = event.getUnmetServiceRequeriments().getNames();
				if(serviceRequerimentsEvaluated.containsAll(unmetServiceReq)){
					result.add(event.getName());
				}
			}
		}else{
			logger.warning("Viewing requirements are evaluated to null. Please check.");
		}
		return result;
	}
	
	
	/**
	 * Funcion encargarda de obtener la lista de Requerimientos de Adaptacion para un evento monitoreado.
	 * @param eventName- identificador del evento monitoreado
	 * @return List- lista con los lista de Requerimientos de Adaptacion para el evento monitoreado.
	 */
	private String getRequirementForEvent(String eventName) {
		DMAdpAdaptationRequirement adpReq = RegistryManager.getInstance().getAdpRequirementForEvent(eventName);
		if (adpReq == null){
			logger.log(Level.WARNING, "There are no adaptation requirement for event " + eventName);
			return null;
		}else{
			return adpReq.getName();
		}
	}
	
	
	/**
	 * Funcion encragada de obtener las estrategias soportadas para un requerimeinto dado.
	 * @param requerimentId Id del requerimeinto.
	 * @return List Retorna una lista con los id de las estratgias soportadoas para un requereimiento.
	 */
	private List<String> getStrategiesSupported(String requirementName) {
		List<String> result = new LinkedList<String>();
		List<DMAdpStrategy> strategies = RegistryManager.getInstance().getAdpStrategiesForRequirement(requirementName);
		for(DMAdpStrategy aux : strategies){
			result.add(aux.getName());
		}
		
		if (result.isEmpty()){
			logger.log(Level.WARNING, "There are no strategies to implement the adaptation requirement " + requirementName);
		}
		
		return result;
	}

	
	/**
	 * Funcion encaragda de obtener el arbol dada un nombre de estrategia, servicio y propiedades.
	 * @param serviceId
	 * @param strategyName
	 * @param equivalents
	 * @param properties
	 * @return AdpFlowTree Retorna el arbol dada una estrategia.
	 * @throws Exception En caso de error al invocar la operacion JMX retorna error.
	 */
	@SuppressWarnings("unchecked")
	private GenericTreeNode<AdpFlowInterface> getAdpFlowTreeForStrategy(DMRegisteredService service, String strategyName, List<DMRegisteredService> equivalents, PropertiesMonitoring properties) throws Exception {
		IAdpStrategy strategy = RegistryManager.getInstance().getAdpStrategyInstance(strategyName);
		Object result = strategy.getAdpTree(service, equivalents, properties);
		if(result != null){
			return (GenericTreeNode<AdpFlowInterface>) result;
		}
		return null;
	}

}
